package com.xing.mybatis;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.jdbc.SQL;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class GeneralSqlBuilder {
    private static ObjectMapper objectMapper = new ObjectMapper();

    // 仅用于生成测试用数据表
    private static String getTypeString(Class<?> type, String property) {
        try {
            String getter = "get" + property.substring(0, 1).toUpperCase() + property.substring(1);
            Class<?> propType = type.getMethod(getter).getReturnType();
            if (propType.isAssignableFrom(Boolean.class)) {
                return "tinyint";
            } else if (propType.isAssignableFrom(Integer.class)) {
                return "int";
            } else if (propType.isAssignableFrom(Long.class)) {
                return "bigint";
            } else if (propType.isAssignableFrom(Double.class)) {
                return "double";
            } else {
                return "varchar(120)";
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public static Map<String, Object> toPlainMap(Object row) {
        try {
            String json = objectMapper.writeValueAsString(row);
            return objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {
            });
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public static String property2Column(String property) {
        return property.replaceAll("([A-Z])", "_$1").toLowerCase().replaceAll("^_", "");
    }


    public static String buildTestCreateTableSql(Class<?> type, String tableName) {
        Object instance;
        try {
            instance = type.newInstance();
        } catch (IllegalAccessException | InstantiationException e) {
            throw new RuntimeException(e.getMessage());
        }
        Map<String, Object> map = toPlainMap(instance);
        List<String> lines = new LinkedList<>();
        for (String key : map.keySet()) {
            if (!key.equals("id")) {
                lines.add(property2Column(key) + " " + getTypeString(type, key));
            }
        }
        return "create table " + tableName + " (id bigint primary key auto_increment, " + String.join(",", lines) + ")";
    }

    public static <T> String buildSimpleInsert(T row, String table) {
        Map<String, Object> map = toPlainMap(row);
        return new SQL() {{
            INSERT_INTO(table);
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                if (entry.getValue() != null) {
                    String col = property2Column(entry.getKey());
                    String val = "#{row." + entry.getKey() + "}";
                    VALUES(col, val);
                }
            }
        }}.toString();
    }

    public static <T> String buildSimpleInsert(T row, String table, String keyProp) {
        Map<String, Object> map = toPlainMap(row);
        return new SQL() {{
            INSERT_INTO(table);
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                if (!entry.getKey().equals(keyProp) && entry.getValue() != null) {
                    String col = property2Column(entry.getKey());
                    String val = "#{row." + entry.getKey() + "}";
                    VALUES(col, val);
                }
            }
        }}.toString();
    }

    public static <T> String buildSimplePatch(T row, String table, String keyProp) {
        Map<String, Object> map = toPlainMap(row);
        if (map.values().stream().filter(Objects::nonNull).collect(Collectors.toList()).size() == 0) {
            return "";
        }
        return new SQL() {{
            UPDATE(table);
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                if (!entry.getKey().equals(keyProp) && entry.getValue() != null) {
                    String col = entry.getKey().replaceAll("([A-Z])", "_$1").toLowerCase().replaceAll("^_", "");
                    String val = "#{row." + entry.getKey() + "}";
                    SET(col + " = " + val);
                }
            }
            WHERE(property2Column(keyProp) + " = #{row." + keyProp + "}");
        }}.toString();
    }
}
